import os, sys
import struct
import hashlib

class FileBlockDev:
    def __init__(self, id, block_size=None, num_blocks=None):
        self.block_path = os.path.join('.', 'blocks')
        self.block_id_path = os.path.join(self.block_path, id)
        if not os.path.exists(self.block_path):
            os.mkdir(self.block_path)

        if not os.path.exists(self.block_id_path):
            new = True
            os.mkdir(self.block_id_path)
        else:
            new = False
            block_size, num_blocks = self.blockinfo()
        
        if (new is True) and ((block_size is None) or (num_blocks is None)):
            os.rmdir(self.block_id_path)
            raise ValueError
        
        self.block_size = block_size
        self.data = block_size * num_blocks

        if new:self.create()

        block_size, num_blocks = self.blockinfo()
        self.block_size = block_size
        self.data = block_size * num_blocks
    
    def blockinfo(self):
        num_blocks = len(os.listdir(self.block_id_path))
        with open(os.path.join(self.block_id_path, '0.block'), 'rb') as b:
            block_size = len(b.read())
        return block_size, num_blocks

    def erase(self, num):
        file_path = os.path.join(self.block_id_path, str(num)+'.block')
        white_block = bytes(bytearray(self.block_size))
        with open(file_path, 'wb') as b:
            b.write(white_block)

    def create(self):
        for i in range(self.data // self.block_size):
            try:self.erase(i)
            except:return
        
    def readblocks(self, block_num, buf, offset=0):
        if block_num + 1 > (self.data // self.block_size):
            raise MemoryError

        block_path = os.path.join(self.block_id_path, str(block_num)+'.block')
        with open(block_path, 'rb') as b:
            block = bytearray(b.read())
        
        block_can_read = self.block_size - offset
        more_buf = block_can_read - len(buf)
        read_in_once = more_buf >= 0

        if read_in_once is False:
            buf = bytearray(block_can_read)

        addr = offset
        for i in range(len(buf)):
            buf[i] = block[addr + i]
        
        if read_in_once is False:
            need_buf = bytearray(-more_buf)
            self.readblocks(block_num + 1, need_buf, 0)
            buf += need_buf
    
    def writeblocks(self, block_num, buf, offset=0):
        block_can_write = self.block_size - offset
        if block_can_write < 0:
            raise IndexError
        more_buf = block_can_write - len(buf)
        write_in_once = more_buf >= 0

        if write_in_once is False:
            over_buf = buf[block_can_write:]
            buf = buf[:block_can_write]

        block = bytearray(self.block_size)
        self.readblocks(block_num, block)
        addr = offset
        for i in range(len(buf)):
            block[addr + i] = buf[i]
        
        file_path = os.path.join(self.block_id_path, str(block_num)+'.block')
        with open(file_path, 'wb') as b:
            b.write(block)
        
        if write_in_once is False:
            self.writeblocks(block_num + 1, over_buf, 0)

class RAMFileBlockDev:
    def __init__(self, block_size, num_blocks, block_id=None):
        self.block_size = block_size
        self.data = bytearray(block_size * num_blocks)
        if block_id:
            self.file = FileBlockDev(block_id, block_size, num_blocks)
            block_size, num_blocks = self.file.blockinfo()
            self.block_size = block_size
            self.data = bytearray(block_size * num_blocks)
            if num_blocks > 1000:
                sys.setrecursionlimit(3000)
            self.loadblocks()
    
    def blockinfo(self):
        return self.file.blockinfo()

    def readblocks(self, block_num, buf, offset=0):
        addr = block_num * self.block_size + offset
        for i in range(len(buf)):
            buf[i] = self.data[addr + i]

    def writeblocks(self, block_num, buf, offset=0):
        addr = block_num * self.block_size + offset
        for i in range(len(buf)):
            self.data[addr + i] = buf[i]
    
    def loadblocks(self):
        if self.file:
            self.file.readblocks(0, self.data, 0)
    
    def saveblocks(self):
        if not self.file:return
        self.file.writeblocks(0, self.data, 0)
    
    def hashsave(self):
        if not self.file:return
        base_dir = self.file.block_id_path
        base_bl = '/{num}.block'
        block_num = self.file.data // self.block_size
        for b in range(block_num):
            block_path = base_dir + base_bl.format(num=b)
            with open(block_path, 'rb') as bf:
                block = bf.read()
                block_md5 = hashlib.md5(block).hexdigest()
                ram = self.data[self.block_size*b:self.block_size*(b+1)]
                ram_md5 = hashlib.md5(bytes(ram)).hexdigest()
                if block_md5 == ram_md5:continue
            
            with open(block_path, 'wb') as bf:
                bf.write(ram)



a = FileBlockDev("rasp_0", 1048576, 256, )

print('Hash ---')

